class Compiler {
  constructor(vm) {
    this.el = vm.$el;
    this.vm = vm;

    this.compile(this.el);
  }

  /**
   * @param {*} el
   * 编译模板，处理文本节点和元素节点
   */
  compile(el) {
    // 遍历了 el 中所有节点
    const childNodes = el.childNodes

    // childNodes 是伪数组，Array.from 转换成数组
    Array.from(childNodes).forEach(e => {
      if(this.isTextNode(e)) {
        this.compileText(e);
      } else if(this.isElementNode(e)) {
        this.compileElement(e);
      }

      // 判断是否有子节点
      if(e.childNodes && e.childNodes.length) {
        this.compile(e)
      }
    });
  }

  /**
   * @param {*} node
   * 编译元素节点，处理指令
   */
  compileElement(node) {
    const attributes = node.attributes;

    Array.from(attributes).forEach(attr => {
      // attributes 是字符串形式的名/值对
      let attrName = attr.name;

      if (this.isDirective(attrName)) {
        attrName = attrName.substr(2);
        const key = attr.value;   
        const event = this.eventName(attrName);
        
        if (event) {
          this.onUpdate(node, event, key);
          return;
        }
        
        this.update(node, this.vm[key], attrName, key);

        new Watcher(this.vm, key, (newValue) => {
          this.update(node, newValue, attrName, key);
        })
      }
    })
  }

  /**
   * @param {*} node node 节点
   * @param {*} value 需要更新的值
   * @param {*} attrName 指令名称
   * @param {*} key 指令对应的值
   * 更新对应指令的值到DOM上
   */
  update(node, value, attrName, key) {
    const fn = this[`${attrName}Update`];
    // call 为了保证 modelUpdate 中的 this 不丢失
    fn && fn.call(this, node, value, key);
  }

  /**
   * @param {*} node
   * @param {*} value
   * 更新v-text的值
   */
  testUpdate(node, value) {
    node.textContent = value
  }

  /**
   * @param {*} node
   * @param {*} value
   * v-model 实际是语法糖，更新表单的value值
   */
  modelUpdate(node, value, key) {
    node.value = value

    // 实现 input 改变 vm 的值  --> 双向绑定
    node.addEventListener('input', () => {
      // 赋值给 vm 实例，vm 改变则触发响应式机制
      this.vm[key] = node.value;
    })
  }

  /**
   * @memberof Compiler
   * v-html 处理 html 字符串，渲染页面
   */
  htmlUpdate(node, value) {
    // 更新元素的 innerHTML
    node.innerHTML = value;
  }

  /**
   * @param {*} node
   * 编译文本节点，处理差值表达式
   */
  compileText(node) {
    const reg = /\{\{(.+?)\}\}/
    let value = node.textContent;

    if (reg.test(value)) {
      const key = RegExp.$1.trim();
      node.textContent = this.vm[key];

      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue
      })
    }
  }

  /**
   * @param {*} node node节点
   * @param {*} event 事件名称
   * @param {*} key methods对应的事件名
   * 处理v-on事件，执行methods对应的方法
   */
  onUpdate(node, event, key) {
    const vm = this.vm;
    this.on(node, event, () => {
      vm.$options.methods[key]();
    })
  }

  /**
   * @param {*} el dom节点
   * @param {*} event 事件名称
   * @param {*} cb 回调函数
   * 把事件指令绑定到DOM事件
   */
  on(el, event, cb) {
    el.addEventListener(event, cb)
  }

  /**
   * @param {*} attrName 指令名称
   * @return {*} 返回事件名称
   * 处理v-on&@指令，返回对应事件名称
   */
  eventName(attrName) {
    const onRE = /^on:|^@/

    if (onRE.test(attrName)) {
      return attrName.replace(onRE, '')
    }
    return ''
  }

  /**
   * @param {*} attrName
   * 处理元素属性是否指令
   */
  isDirective(attrName) {
    return attrName.startsWith('v-')
  }

  /**
   * @param {*} node
   * 判断节点是否是文本节点
   */
  isTextNode(node) {
    // 3 表示文本节点
    return node.nodeType === 3
  }

  /**
   * @param {*} node
   * 判断节点是否是元素节点
   */
  isElementNode(node) {
    // 1 表示元素节点
    return node.nodeType === 1
  }
}